const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(exefn) {
    try {
      exefn(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  status = PENDING

  value = undefined
  reason = undefined

  successCallback = []
  failCallback = []

  resolve = value => {
    // status 不是PENDING，则不再执行
    if (this.status !== PENDING) return;

    // 改变状态为 fulfilled
    this.status = FULFILLED;

    // 保存执行器成功时传入的值
    this.value = value;

    // 兼顾同步。所以先判断 successCallback 是否存在，存在则执行
    // this.successCallback && this.successCallback(this.value)

    // 执行其中有异步时，依次执行所有 then 的成功回调
    while (this.successCallback.length) {
      // shift 会取得数组中的第一个元素并返回，且改变原数组
      // 但 this.successCallback.length 为 0 时，结束 while 循环
      this.successCallback.shift()()
    }
  }
  reject = reason => {
    // status 不是PENDING，则不再执行
    if (this.status !== PENDING) return;

    // 改变状态为 rejected
    this.status = REJECTED;

    // 保存执行器失败时传入的错误原因
    this.reason = reason;

    // 兼顾同步。所以先判断 failCallback 是否存在，存在则执行
    // this.failCallback && this.failCallback(this.reason)

    // 执行其中有异步时，依次执行所有 then 的失败回调
    while (this.failCallback.length) {
      // shift 会取得数组中的第一个元素并返回，且改变原数组
      // 但 this.failCallback.length 为 0 时，结束 while 循环
      this.failCallback.shift()()
    }
  }

  then(successCallback, failCallback) {
    // 处理 then 的参数为可选的情况
    // 这里的 value、reason 为形参
    successCallback = successCallback ? successCallback : value => value;
    failCallback = failCallback ? failCallback : reason => { throw reason };

    let returnPromise = new MyPromise((resolve, reject) => {

      // 判断 status 状态，来决定调用的回调
      if (this.status === FULFILLED) {
        /**
         * 直接在 judgeCallbackReturn 中传递 returnPromise 是有问题的
         * returnPromise 需要在 new MyPromise() 完成后在有值，所以这里使用 setTimeout 异步一下
         */
        setTimeout(() => {
          try {
            let successReturn = successCallback(this.value);
            judgeCallbackReturn(returnPromise, successReturn, resolve, reject);
          } catch (error) {
            reject(error)
          }
        }, 0);

      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let failReturn = failCallback(this.reason);
            judgeCallbackReturn(returnPromise, failReturn, resolve, reject);
          } catch (error) {
            reject(error)
          }
        }, 0);
      } else {
        // 执行器中有异步，status 仍为 pending 时
        // 对于同一 MyPromise 实例多次使用 then，在 pending 时，将回调存入相应的暂存回调数组中，以保证每一个回调都会在后续中执行

        // this.successCallback.push(successCallback);
        // this.failCallback.push(failCallback);

        // 因为 then 的链式调用需要用 resolve，所以暂存回调数组中的回调函数改成一下形式
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let successReturn = successCallback(this.value);
              judgeCallbackReturn(returnPromise, successReturn, resolve, reject);
            } catch (error) {
              reject(error)
            }
          }, 0);
        });
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let failReturn = failCallback(this.reason);
              judgeCallbackReturn(returnPromise, failReturn, resolve, reject);
            } catch (error) {
              reject(error)
            }
          }, 0);
        });
      }
    })

    return returnPromise;
  }

  finally(callback) {
    // 使用 then 得到当前 MyPromise 的状态
    // 在 then 的 resolve/reject 中调用 callback 则无论 MyPromise 是否成功, 都会执行
    // then 会返回一个 MyPromise 对象，所以 return 后，finally 后面就可以再接 then
    return this.then(res => {
      // 在 finally 后调用then，该 then 会拿到 finally 返回的 MyPromise 的结果，所以这里要 return res
      // callback();
      // return res

      // 若 callback 有异步，则用 MyPromise.resolve() 把 callback 变成 MyPromise，
      // 然后在其后面调用 then，并在其成功回调中返回 res
      // 这样就可以确保 callback 执行完毕后再返回当前 MyPromise 的结果
      return MyPromise.resolve(callback()).then(() => res)
    }, err => {
      // 失败时，需要 throw err，如此才能将错误信息传递给 finally 后面调用的 then
      // callback();
      // throw err

      // 用 MyPromise.resolve() 把 callback 变成 MyPromise，调用 then，并在其成功回调中 throw err 来传递错误
      return MyPromise.resolve(callback()).then(() => {throw err})
    })
  }

  catch(failCallback) {
    // 使用 then 得到当前 MyPromise 的状态，若失败，则执行 catch 的回调
    // 使用 return 返回 MyPromise 对象，以便 catch 后面链式调用 MyPromise 其他方法
    return this.then(undefined, failCallback)
  }

  static all(array) {
    let results = []; // 存储 array 中每项的结果
    let index = 0; // 记数处理完成的个数

    // 返回 MyPromise 对象
    return new MyPromise((resolve, reject) => {
      function addResult(key,value) {
        results[key] = value;
        index++;

        if(index === array.length) {
          resolve(results)
        }
      }

      // 循环获取 array 中的每一项
      for (let i = 0; i < array.length; i++) {
        let current = array[i];
        // 判断 current 是 普通值 还是 MyPromise 对象
        if (current instanceof MyPromise) {
          // 因为 current 作为 MyPromise 对象，其中可能存在异步的情况
          // 而 for 循环瞬间完成，current.then 的回调还来不及执行，这会造成 result 中有空值

          // 为解决异步问题，这里在 all 中添加一个 index 变量作为记数，定义 addResult(key,value) 函数作为将结果保存进 results 的操作
          // addResult 没执行一次，index 加一， 当 index 等于 array.length 时，代表所有项已经执行完成，此时调用 resolve
          current.then(res => { addResult(i, res) }, reject)
        } else {
          addResult(i, current)
        }
      }
    })
  }

  static resolve(value) {
    // 判断是否为 MyPromise 对象
    // 是，则直接返回
    if(value instanceof MyPromise) return value;

    // 否，返回一个成功的 MyPromise
    return new MyPromise(resolve => resolve(value))
  }
}

/**
 * 新建函数来统一处理对返回值的判断
 * 
 * 1、判断 returnValue 是否为 returnPromise，是则直接抛出异常
 * 2、判断 returnValue 是否为 MyPromise 对象
 *    是，则使用 then 判断 returnValue 的状态，回调为 returnPromise 的 resolve, reject
 *    否，则使用 resolve
 */
function judgeCallbackReturn(returnPromise, returnValue, resolve, reject) {
  if (returnValue === returnPromise) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  if (returnValue instanceof MyPromise) {
    returnValue.then(resolve, reject)
  } else {
    resolve(returnValue)
  }

}

module.exports = MyPromise